﻿using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Text;
using System.IO;
using System.Diagnostics;

namespace HB.WorkFlow
{
    public class WorkFlow<T> where T : class
    {
        /// <summary>
        /// 日志事件，string sampleId, string msg
        /// </summary>
        public event Action<string, string> OnLogEvent;
        /// <summary>
        /// 记录日志并出发外部通知
        /// </summary>
        /// <param name="sampleId">样本id</param>
        /// <param name="msg">消息</param>
        void OnLog(string sampleId, string msg)
        {
            OnLogEvent?.Invoke(sampleId, msg);
        }
        /// <summary>
        /// 流程
        /// </summary>
        /// <param name="name">流程名</param>
        /// <param name="filter">如果过滤器为空，流程发生问题时将直接抛出，可能导致流程停转</param>
        public WorkFlow(string name, IStepFilter filter = null)
        {
            this.Name = name;
            Filter = filter;
            DicStep = new Dictionary<string, StepBody<T>>();
        }
        Dictionary<string, DateTime> DoingList = new Dictionary<string, DateTime>();
        public string Name { get; set; }
        T Data { get; set; }
        Dictionary<string, StepBody<T>> DicStep { get; set; }
        IStepFilter Filter { get; set; }

        /// <summary>
        /// 建立节点
        /// </summary>
        /// <typeparam name="step"></typeparam>
        /// <returns></returns>
        public StepRouter<T> BuildStep<step>() where step : IStep<T>, new()
        {
            string id = typeof(step).Name;
            if (DicStep.ContainsKey(id))
            {
                throw new Exception("Step already Exists");
            }
            else
            {
                var stepclass = new step();
                DicStep[id] = new StepBody<T>() { Step = stepclass, Id = id };
                return DicStep[id].Router;
            }
        }
        /// <summary>
        /// 开始流程中的指定节点
        /// </summary>
        /// <typeparam="step"></typeparam>
        /// <param name="data">必须传入数据实例</param>
        /// <param name="sampleId">样品Id</param>
        public void Start<step>(T data, string sampleId) where step : IStep<T>
        {
            if (data == null)
            {
                throw new Exception("Data can not be null");
            }
            if (sampleId == null)
            {
                throw new Exception("SampleId can not be null");
            }
            string id = typeof(step).Name;
            if (DicStep.ContainsKey(id))
            {
                this.Data = data;
                Filter?.WorkFlowStart(this, DicStep[id], Data, sampleId);
                DoStep(DicStep[id], sampleId);
            }
            else
            {
                throw new Exception($"No Such Step Named {id}");
            }
        }
        /// <summary>
        /// 执行流程方法
        /// </summary>
        /// <param name="body"></param>
        void DoStep(StepBody<T> body, string sampleId)
        {
            if (body == null) { return; }
            Task.Factory.StartNew(() =>
            {
                Filter?.StepExecuting(body, Data);//执行前
                string doingGuid = Guid.NewGuid().ToString();
                OnLog(sampleId, $"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")}: Start {body.Step.Name} ");
                try
                {
                    DoingList.Add(doingGuid, DateTime.Now);
                    body.Step.Do(Data).Wait();
                }
                catch (Exception ex)
                {
                    Filter?.OnErr(ex);
                    if (Filter == null)//如果没有过滤器，必须找个地方抛出来
                    {
                        throw ex;
                    }
                }
                finally
                {
                    DoingList.Remove(doingGuid);
                    OnLog(sampleId, $"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")}: End {body.Step.Name} ");
                }
                Filter?.StepExecuted(body, Data);//执行后
                var nextSteps = body.Router.GetNextStepByData(Data);
                if (nextSteps.Count > 0)
                {
                    foreach (var step in nextSteps)
                    {
                        if (DicStep.ContainsKey(step.StepId))
                        {
                            Filter?.RouteNavigated(body, Data, step.StepId);
                            DoStep(DicStep[step.StepId], sampleId);
                        }
                        else
                        {
                            throw new Exception($"Step no found:{step.StepId},you need to build it first");
                        }
                    }
                }
                else
                {
                    OnLog(sampleId, $"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")}: Workflow End by {body.Step.Name} ");
                    Filter?.WorkFlowEnd(this, body, Data, sampleId);
                }
            });
        }
        /// <summary>
        /// 获取markdown流程图
        /// </summary>
        /// <returns></returns>
        public string GetFlowChartCode()
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine($"flowchart  TD");
            foreach (var step in DicStep.Values)
            {
                var routers = step.Router.GetRouters();
                int conditionCount = routers.Count;
                string left = conditionCount > 1 ? "{" : "(";
                string right = conditionCount > 1 ? "}" : ")";
                sb.AppendLine($"{step.Id}{left}{step.Step.Name}{right}");
                foreach (var rou in routers)
                {

                    sb.AppendLine($"{step.Id}--{rou.ConditionText}--> {rou.StepId}");
                }
            }
            return sb.ToString();

        }
        public void ShowFlowChartView()
        {
            var dir = Path.Combine(Path.GetTempPath(), "HBWorkFlow");
            Directory.CreateDirectory(dir);
            var jsfile = Path.Combine(dir, "mermaid.min.js");
            if (!File.Exists(jsfile))
            {
                File.WriteAllText(jsfile, HB.WorkFlow.Properties.Resources.mermaid_min);
            }
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("<html><body>");
            sb.AppendLine("<script src=\"mermaid.min.js\"></script>");
            sb.AppendLine("<script>mermaid.initialize({startOnLoad:true});</script>");
            sb.AppendLine($"<h2 align='center'>流程:{Name}</h2>");
            sb.AppendLine("<div class=\"mermaid\" align='center'>");
            sb.AppendLine(GetFlowChartCode());
            sb.AppendLine("</div></body></html>");
            var htmlfile = Path.Combine(dir, $"{Name}.html");
            File.WriteAllText(htmlfile, sb.ToString());
            Process.Start("explorer.exe", htmlfile);
        }
    }
}
